using System;
using Heaven.Mathematics;

namespace Heaven.Engine
{
    /// <summary>
    /// This is the implementation of camera 
    /// which allows us to rotate the observer 
    /// around a target like on an orb
    /// </summary>
    public class CameraBrowser : CameraPerspective
    {
        #region Cache

        // View matrix
        bool viewMatrixCached = false;
        Matrix4 viewMatrix;

        #endregion

        #region Fields

        // Distance to the object
        double radius = 1.0f;

        // Angles, in radians
        double verticalRotation = 0.0f;
        double horisontalRotation = 0.0f;

        #endregion

        #region Properties

        /// <summary>
        /// The final matrix of view
        /// </summary>
        public override Matrix4 View
        {
            get
            {
                if(!viewMatrixCached)
                {
                     Vector3 up = verticalRotation > Math.PI / 2 && verticalRotation < Math.PI / 2 * 3 ?
                         -Vector3.YAxis :
                          Vector3.YAxis;
                     viewMatrix =  Matrix4.LookAt(Position, target, up);
                     viewMatrixCached = true;
                }
                return viewMatrix;                
            }            
        }

        /// <summary>
        /// Angle around target, in radians
        /// </summary>
        public double HorisontalRotation
        {
            get { return horisontalRotation; }
            set
            {
                horisontalRotation = value;

                // Keeps value between 0 and 2*PI 
                horisontalRotation = horisontalRotation > (float)Math.PI * 2 ? horisontalRotation - (float)Math.PI * 2 : horisontalRotation;
                horisontalRotation = horisontalRotation < 0 ? horisontalRotation + (float)Math.PI * 2 : horisontalRotation;
                Invalidate();
            }
        }

        /// <summary>
        /// Angle up/down, in radians
        /// </summary>
        public double VerticalRotation
        {
            get { return verticalRotation; }
            set
            {
                verticalRotation = value;

                // Keeps value between 0 and 2*PI 
                verticalRotation = verticalRotation > (float)Math.PI * 2 ? verticalRotation - (float)Math.PI * 2 : verticalRotation;
                verticalRotation = verticalRotation < 0 ? verticalRotation + (float)Math.PI * 2 : verticalRotation;
                Invalidate();
            }
        }

        /// <summary>
        /// The distance to the target
        /// </summary>
        public double Distance
        {
            get { return radius; }
            set
            {
                radius = value;
                if (radius < 0.01f) radius = 0.01f;
                Invalidate();
            }
        }

        /// <summary>
        /// The observer's position
        /// </summary>
        public override Vector3 Position
        {
            get
            {
                Vector3 position = Vector3.Zero;
                position.X = (float)(radius * Math.Cos(verticalRotation) * Math.Cos(horisontalRotation));
                position.Y = (float)(radius * Math.Sin(verticalRotation));
                position.Z = (float)(radius * Math.Cos(verticalRotation) * Math.Sin(horisontalRotation));
                position += target;
                return position;
            }
            set
            {
                throw new NotSupportedException("Browser camera doesn't support position setup. In case of that type of camera this property is dependent");
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructot
        /// </summary>
        public CameraBrowser()
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">The target which directs the camera</param>
        /// <param name="radius">Distance to the target</param>
        public CameraBrowser(Vector3 target, double radius)
        {
            this.target = target;
            this.radius = radius;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="target">The target which directs the camera</param>
        /// <param name="radius">Distance to the target</param>
        /// <param name="horisontalRotation">Angle around target, in radians</param>
        /// <param name="verticalRotation">Angle up/down, in radians</param>
        public CameraBrowser(Vector3 target, double radius, double horisontalRotation, double verticalRotation)
        {
            this.target = target;
            this.radius = radius;
            this.horisontalRotation = horisontalRotation;
            this.verticalRotation = verticalRotation;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates object
        /// </summary>
        public override void Invalidate()
        {
            // Reset cache
            viewMatrixCached = false;
            // Invoke the base method
            base.Invalidate();
        }

        #endregion
    }
}
